package kg.aibek.dc;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

public class ConditionalAdaptiveEncoder implements ArithmeticEncoder {
	private int low = 0, high = (int) Math.pow(2, AdaptiveModel.BIT_LENGTH) - 1;
	private int quarter = (int) Math.pow(2, AdaptiveModel.BIT_LENGTH - 2);

	Map<Integer, Double> rateMap = new HashMap<Integer, Double>();
	private Double compressionRate;

	private String encodedStr = "";

	public void encode(String filePath) throws IOException {
		Map<Character, AdaptiveModel> modelMap = new HashMap<Character, AdaptiveModel>();

		FileInputStream fis = new FileInputStream(filePath);
		Reader reader = new InputStreamReader(fis);
		int scale = 0;

		int r;
		int counter = 0;
		boolean finish = false;

		Character prev = null;

		while (!finish) {
			if ((r = reader.read()) == -1) {
				finish = true;
				r = 3;
			}

			if (r >= 65 && r <= 90) {
				r += 32;
			}

			if (modelMap.get(prev) == null) {
				modelMap.put(prev, new AdaptiveModel());
			}
			AdaptiveModel model = modelMap.get(prev);

			counter++;

			int[] cumCount = model.getCumulativeFrequency();
			int totalCount = model.getTotalFrequency();

			int range = high - low + 1;
			int newLow = low + (int) (range * cumCount[r - 1] / (double) totalCount);
			int newHigh = low + (int) (range * cumCount[r] / (double) totalCount) - 1;
			low = newLow;
			high = newHigh;

			while (true) {
				int msbLow = Utility.getBitAtPosition(low, 0);
				int msbHigh = Utility.getBitAtPosition(high, 0);

				if (msbLow == msbHigh) {
					if (msbLow == 1) {
						encodedStr += "1";
						while (scale > 0) {
							encodedStr += "0";
							scale--;
						}
					} else {
						encodedStr += "0";
						while (scale > 0) {
							encodedStr += "1";
							scale--;
						}
					}

					low = Utility.shiftMSB(low, 0);
					high = Utility.shiftMSB(high, 1);
				} else {
					if (Utility.getBitAtPosition(low, 1) == 1
							&& Utility.getBitAtPosition(high, 1) == 0) {
						low -= quarter;
						high -= quarter;
						low = Utility.shiftMSB(low, 0);
						high = Utility.shiftMSB(high, 1);
						scale++;
					} else {
						break;
					}
				}
			}

			// Rate Calculation
			if (counter % 10 == 0) {
				rateMap.put(counter, encodedStr.length() / (double) counter);
			}

			model.addContext((char) r);
			prev = (char) r;
		}
		compressionRate = (encodedStr.length() / (double) 8) * 100 / (double) counter;
	}

	public String getEncodedStr() {
		return encodedStr;
	}

	public Map<Integer, Double> getRate() {
		return rateMap;
	}

	public Double getCompressionRate() {
		return compressionRate;
	}
}
